import getpass
import platform
import click
import os
import logging
import shutil
from src.exceptions import AuthorizationException

if platform.system() == "Windows":
  CONFIG_HOME = os.environ['APPDATA']
else:
  import xdg
  CONFIG_HOME = xdg.XDG_CONFIG_HOME


PASSPY_AVAILABLE = True
try:
  import passpy
except ImportError:
  PASSPY_AVAILABLE = False

LOG = logging.getLogger("philly")


class PasswordStorage:
  def __init__(self):
    self.gpgbinary = shutil.which(os.environ.get("PT_GPG_BINARY", "gpg2"))
    if self.gpgbinary is None:
      self.gpgbinary = shutil.which("gpg")

    self.gpg = None
    if self.gpgbinary and PASSPY_AVAILABLE:
        self.gpg = passpy.Store(gpg_bin=self.gpgbinary, interactive=True)

  def has_passpy(self, key=None):
    res = self.gpg is not None and self.gpg.is_init()
    if key is not None:
      return res and self.gpg.find(key)

  def retrieve(self, user, interactive=True):
    if self.gpg:
      if not self.gpg.is_init():
        LOG.error("Passpy is installed, but store is not initialized.")
      else:
        try:
          pwd = self.gpg.get_key(user).strip()
          if pwd == '':
            LOG.error("Got empty password from passpy, did you set up GPG_TTY correctly?")
          else:
            LOG.info("Using password from passpy for '%s'", user)
            return pwd
        except FileNotFoundError:
          LOG.error("Passpy is installed, but no password was stored for '%s'.",
                    user)

    if not interactive:
      return None

    pwd = click.prompt("Password for %s" % user, hide_input=True)
    self.conditional_store(user, pwd)
    return pwd

  def conditional_store(self, user, password):
    if ((not self.gpg or
         not self.gpg.is_init() or
         not click.confirm("Store encrypted password for %s" % user))):
      return
    self.store(user, password)

  def store(self, user, password):
    if self.gpg:
      self.gpg.set_key(user, password, force=True)
      LOG.info("Password for %s stored in %s", user, self.gpg.store_dir)


def retry_with_other_password(f):
  def retry_with_auth(*args, **kwargs):
    while True:
      try:
        return f(*args, **kwargs)
      except AuthorizationException as e:
        LOG.error("AuthorizationException: %s", e)
        self = args[0]
        if not getattr(self, "_interactive", False) or not click.confirm("Change password", prompt_suffix="? "):
          raise
        store = PasswordStorage()
        store.clear()
        self.password = store.retrieve(self.username)
  return retry_with_auth


class Credentials(object):
  """Holds the credentials for the session.

  This class is a singleton with lazy init instantiated once throughout the execution.
  """
  __instance = None

  def __init__(self, username=None, password=None, insecure=False, cert=None, domain_joined=False):
    self.username = username
    self.password = password
    self.insecure = insecure
    self.cert = cert
    self.domain_joined = domain_joined
    self.username_file = os.path.join(CONFIG_HOME, "phillytools", "philly_user")

  def _get_system_user(self):
    user = self._extract_alias(getpass.getuser())
    return user

  def _extract_alias(self, user):
    # removes the "DOMAIN." part (if it exists) from "DOMAIN.alias"
    # removes the "@domain.com" part if it exists
    pos = user.find('@')
    if pos >= 0:
      user = user[:pos]
    pos = user.find('.')
    if pos >= 0:
      user = user[pos + 1:]
    return user

  def reset_stored_user(self):
    if os.path.exists(self.username_file):
      os.unlink(self.username_file)

  def _ask_for_unset_values(self, requires_password=True):
    if not self.username:
      if os.path.exists(self.username_file):
        with open(self.username_file, "r") as f:
          self.username = f.read().strip()
      else:
        self.username = click.prompt("Microsoft alias", default=self._get_system_user())
        self.username = self._extract_alias(self.username)
        if click.confirm("Save microsoft alias %s as default to %s" % (self.username, self.username_file)):
          os.makedirs(os.path.dirname(self.username_file), exist_ok=True)
        with open(self.username_file, 'w') as f:
          f.write(self.username)

    if not self.password and requires_password:
      if self.domain_joined:
        return

      store = PasswordStorage()
      self.password = store.retrieve(self.username)

  @classmethod
  def instantiate(cls, username=None, password=None, insecure=False,
                  cert=None, domain_joined=False):
    """Lazy instantiation of the singleton.
    """
    cls.__instance = Credentials(username, password,
                                 insecure=insecure, cert=cert,
                                 domain_joined=domain_joined)

  @classmethod
  def get(cls, requires_password=False):
    if cls.__instance is None:
      raise ValueError("Must perform authentication before proceeding.")

    cls.__instance._ask_for_unset_values(requires_password=requires_password)
    return cls.__instance

  @classmethod
  def instantiated(cls):
    return cls.__instance is not None

  @classmethod
  def reset(cls):
    cls.__instance = None
